<?php
/* 
 * Copyright (c) 2011 Antoine d'Otreppe de Bouvette
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/**
 * @property-read Config $config
 * @property-read array $routes
 * @property-read array $routeMapping
 * @property-read Database $database
 * @property-read Request $request
 * @property-read Dispatcher $dispatcher
 * @property-read URLWriter $urlWriter
 * @property-read TemplateProvider $templateProvider
 * @property-read TemplateRenderer $templateRenderer
 * @property-read Logger $logger
 * @property-read Controller $defaultController
 */
abstract class Environment {
    /**
     *
     * @var Environment
     */
    private static $mostRecentInstance;
    
    /**
     * @var array
     */
    private $attributes = array();
    
    /**
     * @return Environment The most recently instanciated Environment
     */
    public static function getInstance() {
        return self::$mostRecentInstance;
    }
    
    public function __construct() {
        self::$mostRecentInstance = $this;
        $this->init();
    }
    
    /**
     * Override me
     */
    protected function init() {
        
    }
    
    protected function parseRoutes($filename) {
        $vars = parse_ini_file($filename, true);
        $this->attributes['routes'] = array_get($vars, 'routes', array());
        $this->attributes['routeMapping'] = array_get($vars, 'mapping',array());
    }
    
    /**
     *
     * @return array
     */
    public function getRoutes() {
        return array(
            '*' => 'default'
        );
    }
    
    /**
     *
     * @return array
     */
    public function getRouteMapping() {
        return array(
            'default' => 'defaultController'
        );
    }
    
    /**
     *
     * @return Config
     */
    public function getConfig() {
        return new Config(array(
            'website.url' => $this->makeWebsiteUrl()
        ));
    }
    
    /**
     *
     * @return string
     */
    protected function makeWebsiteUrl() {
        $port = $_SERVER['SERVER_PORT'] == 80 ? '':':'.$_SERVER['SERVER_PORT'];
        return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
                    ? 'https' : 'http') . '://' .
                $_SERVER['HTTP_HOST'] . $port . $_SERVER['SCRIPT_NAME'];
    }
    
    /**
     *
     * @return Database
     */
    public function getDatabase() {
    	$this->config->required('database.dsn');
    	$config = $this->config;
    	
    	return new SimpleDatabase($config['database.dsn'], array(
    		'user' 		=> array_get($config, 'database.user'),
    		'pass' 		=> array_get($config, 'database.pass'),
    		'prefix'	=> array_get($config, 'database.prefix')
    	));
    }
    
    protected function initDao(BaseDao $dao) {
    	$dao->setDatabase($this->database);
    }
    
    /**
     * @return Request
     */
    public function getRequest() {
        $factory = new StandardRequestFactory();
        return $factory->buildRequest();
    }
    
    /**
     *
     * @return Dispatcher 
     */
    public function getDispatcher() {
        return new Dispatcher($this, $this->routes, $this->routeMapping);
    }
    
    /**
     * This is the controller used when a route is not found
     * 
     * @return NotFoundController 
     */
    public function getDefaultController() {
        return new NotFoundController();
    }
    
    /**
     *
     * @return URLWriter 
     */
    public function getUrlWriter() {
        $this->config->required('website.url');
        return new URLWriter($this->config['website.url'], $this->routes);
    }
    
    /**
     *
     * @return TemplateProvider 
     */
    public function getTemplateProvider() {
        $config = $this->config;
        $config->required('template.source');
        
        return new SimpleTemplateProvider(
            $config['template.source'],
            array_get($config, 'template.extension', 'php')
        );
    }
    
    /**
     *
     * @return TemplateRenderer 
     */
    public function getTemplateRenderer() {
        return new SimpleTemplateRenderer($this->templateProvider);
    }
    
    protected function initController(Controller $controller) {
        $controller->setUrlWriter($this->urlWriter);
        $controller->setTemplateRenderer($this->templateRenderer);
    }
    
    /**
     *
     * @return Logger 
     */
    public function getLogger() {
    	$config = $this->config;
        
        if (array_key_exists('log.file', $config) && $config['log.file']) {
            return new FileLogger(
                    $config['log.file'],
                    array_get($config, 'log.level', Logger::WARNING));
        }
    	else {
            return new NullLogger();
        }
    }
    
    public function __get($attr) {
        if (!array_key_exists($attr, $this->attributes)) {
            $method = "get".ucfirst($attr);
            
            if (method_exists($this, $method)) {
                $object = $this->$method();
                $this->attributes[$attr] = $object;
                return $object;
            }
            else {
                throw new RuntimeException(
                    "No '$attr' attribute in " . get_class($this));
            }
        }
        else {
            return $this->attributes[$attr];
        }
    }
    
    public function __isset($attr) {
        return method_exists($this, 'get'.ucfirst($attr));
    }
    
    protected function mergeIniWithParent($filename) {
        return array_merge(parent::getConfig(), parse_ini_file($filename));
    }
}
